package org.quipu.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_RIGHT;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_MIDDLE;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_TOP;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.CalendarUtil;
import com.google.gwt.user.datepicker.client.DateBox;
import java.util.Date;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.quipu.client.events.AppEvent;
import org.quipu.client.events.AppEventBus;
import org.quipu.client.events.AppEventListener;
import org.quipu.client.events.TransactionsFilterChangedEvent;
import org.quipu.dto.AccountDto;
import org.quipu.dto.CategoryDto;
import org.quipu.dto.TransactionsFilterDto;

public class TransactionsFilterPanel extends AppEventWidget implements AppEventListener, ControlChangeHandler<TagsCloud, Long> {

    private final VerticalPanel vPanel;
    private final DateBox dateFromBox;
    private final DateBox dateToBox;
    private final AccountListBox srcAccountListBox;
    private final AccountListBox dstAccountListBox;
    private final Button addAccountsBtn;
    private final List<Pair<AccountDto, AccountDto>> accounts;
    private final Map<CategoryDto.Type, CategoryListBox> categoryListBoxes;
    private final Map<CategoryDto.Type, List<CategoryDto>> categories;
    private final Map<CategoryDto.Type, Button> addCategoryButtons;
    private final CheckBox incomesCheckBox;
    private final CheckBox outcomesCheckBox;
    private final CheckBox transfersCheckBox;
    private final CheckBox approvedOnlyCheckBox;
    private final CheckBox applyOnChangeCheckBox;
    private final TagsCloud tagsCloud;
    private final Button applyFilterBtn;
    private final FlexTable form;

    public TransactionsFilterPanel(AppEventBus eventBus, AppCache cache) {
        super(eventBus, cache);
        vPanel = new VerticalPanel();
        vPanel.addStyleName("controlPanel");
        vPanel.setHorizontalAlignment(ALIGN_LEFT);
        vPanel.setVerticalAlignment(ALIGN_TOP);
        vPanel.setSize("100%", "100%");
        form = new FlexTable();
//        form.setBorderWidth(1);
        FlexTable.FlexCellFormatter cellFormatter = form.getFlexCellFormatter();

        srcAccountListBox = new AccountListBox(eventBus, cache, "Любой", null);
        dstAccountListBox = new AccountListBox(eventBus, cache, "Любой", null);
        addAccountsBtn = new Button("+");
        addAccountsBtn.setStylePrimaryName("AddRemoveBtn");
        addAccountsBtn.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                addAccounts();
            }
        });
        accounts = new LinkedList<Pair<AccountDto, AccountDto>>();

        categories = new EnumMap<CategoryDto.Type, List<CategoryDto>>(CategoryDto.Type.class);
        categories.put(CategoryDto.Type.INCOME, new LinkedList<CategoryDto>());
        categories.put(CategoryDto.Type.OUTCOME, new LinkedList<CategoryDto>());
        categoryListBoxes = new EnumMap<CategoryDto.Type, CategoryListBox>(CategoryDto.Type.class);
        addCategoryButtons = new EnumMap<CategoryDto.Type, Button>(CategoryDto.Type.class);

        ValueChangeHandler<Boolean> fitlerCheckBoxChangeHandler = new ValueChangeHandler<Boolean>() {
            @Override
            public void onValueChange(ValueChangeEvent<Boolean> event) {
                onFilterChange();
            }
        };
        ValueChangeHandler<Date> fitlerDateChangeHandler = new ValueChangeHandler<Date>() {
            @Override
            public void onValueChange(ValueChangeEvent<Date> event) {
                onFilterChange();
            }
        };

        incomesCheckBox = new CheckBox("доходы");
        incomesCheckBox.setValue(true);
        incomesCheckBox.addValueChangeHandler(fitlerCheckBoxChangeHandler);
        outcomesCheckBox = new CheckBox("расходы");
        outcomesCheckBox.setValue(true);
        outcomesCheckBox.addValueChangeHandler(fitlerCheckBoxChangeHandler);
        transfersCheckBox = new CheckBox("переносы");
        transfersCheckBox.setValue(true);
        transfersCheckBox.addValueChangeHandler(fitlerCheckBoxChangeHandler);

        approvedOnlyCheckBox = new CheckBox("только утвержденные");
        approvedOnlyCheckBox.setValue(false);
        approvedOnlyCheckBox.addValueChangeHandler(fitlerCheckBoxChangeHandler);

        Date dateTo = new Date();
        Date dateFrom = CalendarUtil.copyDate(dateTo);
        CalendarUtil.addDaysToDate(dateFrom, -7);
        dateFromBox = new DateBox();
        dateFromBox.addStyleName("dateInput");
        dateFromBox.setFormat(new DateBox.DefaultFormat(new DateTimeFormat("dd.MM.yyyy")));
        dateFromBox.setValue(dateFrom);
        dateFromBox.addValueChangeHandler(fitlerDateChangeHandler);
        dateToBox = new DateBox();
        dateToBox.addStyleName("dateInput");
        dateToBox.setFormat(new DateBox.DefaultFormat(new DateTimeFormat("dd.MM.yyyy")));
        dateToBox.setValue(dateTo);
        dateToBox.addValueChangeHandler(fitlerDateChangeHandler);

        for(final CategoryDto.Type type: CategoryDto.Type.values()) {
            CategoryListBox categoryListBox = new CategoryListBox(getEventBus(), getCache(), type, null);
            categoryListBoxes.put(type, categoryListBox);
            Button addCategoryBtn = new Button("+");
            addCategoryBtn.setStylePrimaryName("AddRemoveBtn");
            addCategoryBtn.addClickHandler(new ClickHandler() {

                @Override
                public void onClick(ClickEvent event) {
                    addCategory(type);
                }
            });
            addCategoryButtons.put(type, addCategoryBtn);
        }

        applyOnChangeCheckBox = new CheckBox("применять немедленно");
        applyOnChangeCheckBox.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                onFilterChange();
            }
        });
        applyOnChangeCheckBox.setValue(false);
        tagsCloud = new TagsCloud(eventBus, cache, false);
        applyFilterBtn = new Button("Применить");
        applyFilterBtn.setStylePrimaryName("pushButton");
        applyFilterBtn.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                fireChangeEvent();
            }
        });
        vPanel.add(form);
        repaintForm();
    }

    private void onFilterChange() {
        if(applyOnChangeCheckBox.getValue()) {
            fireChangeEvent();
        }
    }

    private void addAccounts() {
        AccountDto srcAccount = getCache().getAccount(srcAccountListBox.getValue());
        AccountDto dstAccount = getCache().getAccount(dstAccountListBox.getValue());
        accounts.add(new Pair(srcAccount, dstAccount));
        repaintForm();
        onFilterChange();
    }

    private void removeAccounts(Long srcId, Long dstId) {
        Pair<AccountDto, AccountDto> pair;
        for(Iterator<Pair<AccountDto, AccountDto>> it = accounts.iterator(); it.hasNext();) {
            pair = it.next();
            if(((srcId == null && pair.getFirst() == null) || (pair.getFirst() != null && pair.getFirst().getId().equals(srcId))) &&
                ((dstId == null && pair.getSecond() == null) || (pair.getSecond() != null && pair.getSecond().getId().equals(dstId)))) {
                it.remove();
                break;
            }
        }
        repaintForm();
        onFilterChange();
    }

    private void addCategory(final CategoryDto.Type type) {
        final CategoryDto category = getCache().getCategory(type, categoryListBoxes.get(type).getValue());
        categories.get(type).add(category);
        repaintForm();
        onFilterChange();
    }

    private void removeCategory(CategoryDto.Type type, Long categoryId) {
        for(Iterator<CategoryDto> it = categories.get(type).iterator(); it.hasNext();) {
            if(it.next().getId().equals(categoryId)) {
                it.remove();
                break;
            }
        }
        repaintForm();
        onFilterChange();
    }

    private void repaintForm() {
        form.removeAllRows();
        final FlexTable.FlexCellFormatter cellFormatter = form.getFlexCellFormatter();

        int row = 0;
        form.setWidget(row, 0, incomesCheckBox);
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, outcomesCheckBox);
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, transfersCheckBox);
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, approvedOnlyCheckBox);
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, dateFromBox);
        form.setWidget(row, 1, new Label("..."));
        form.setWidget(row, 2, dateToBox);
        cellFormatter.setColSpan(row++, 2, 2);
        form.setWidget(row, 0, srcAccountListBox.getWidget());
        form.setWidget(row, 1, new Label("->"));
        form.setWidget(row, 2, dstAccountListBox.getWidget());
        form.setWidget(row, 3, addAccountsBtn);
        cellFormatter.setAlignment(row++, 3, ALIGN_RIGHT, ALIGN_MIDDLE);

        for(final Pair<AccountDto, AccountDto> accountsPair: accounts) {
            final Long srcId = accountsPair.getFirst() != null? accountsPair.getFirst().getId(): null;
            final Long dstId = accountsPair.getSecond()!= null? accountsPair.getSecond().getId(): null;
            form.setWidget(row, 0, new Label(accountsPair.getFirst() != null? accountsPair.getFirst().getTitle(): "*"));
            form.setWidget(row, 1, new Label("->"));
            form.setWidget(row, 2, new Label(accountsPair.getSecond() != null? accountsPair.getSecond().getTitle(): "*"));
            Button removeBtn = new Button("x");
            removeBtn.setStylePrimaryName("AddRemoveBtn");
            removeBtn.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    removeAccounts(srcId, dstId);
                }
            });
            form.setWidget(row, 3, removeBtn);
            cellFormatter.setAlignment(row++, 3, ALIGN_RIGHT, ALIGN_MIDDLE);
        }

        for(final CategoryDto.Type type: CategoryDto.Type.values()) {
            form.setWidget(row, 0, categoryListBoxes.get(type).getWidget());
            cellFormatter.setColSpan(row, 0, 3);
            form.setWidget(row, 1, addCategoryButtons.get(type));
            cellFormatter.setAlignment(row++, 1, ALIGN_RIGHT, ALIGN_MIDDLE);

            for(final CategoryDto category: categories.get(type)) {
                form.setWidget(row, 0, new Label(category.getTitle()));
                cellFormatter.setColSpan(row, 0, 3);
                Button removeBtn = new Button("x");
                removeBtn.setStylePrimaryName("AddRemoveBtn");
                removeBtn.addClickHandler(new ClickHandler() {
                    public void onClick(ClickEvent event) {
                        removeCategory(type, category.getId());
                    }
                });
                form.setWidget(row, 1, removeBtn);
                cellFormatter.setAlignment(row++, 1, ALIGN_RIGHT, ALIGN_MIDDLE);
            }
        }
        form.setWidget(row, 0, tagsCloud.getWidget());
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, applyOnChangeCheckBox);
        cellFormatter.setColSpan(row++, 0, 4);
        form.setWidget(row, 0, applyFilterBtn);
        cellFormatter.setColSpan(row, 0, 4);
        cellFormatter.setAlignment(row++, 0, ALIGN_RIGHT, ALIGN_MIDDLE);
    }

    @Override
    public void init() {
        srcAccountListBox.init();
        dstAccountListBox.init();
        for(CategoryListBox categoryListBox: categoryListBoxes.values()) {
            categoryListBox.init();
        }
        tagsCloud.init();
        tagsCloud.addChangeHandler(this);
    }

    @Override
    public Widget getWidget() {
        return vPanel;
    }

    @Override
    public void handleEvent(AppEvent event) {
    }

    public TransactionsFilterDto getFilter() {
        TransactionsFilterDto filter = new TransactionsFilterDto();
        filter.setIncomes(incomesCheckBox.getValue());
        filter.setOutcomes(outcomesCheckBox.getValue());
        filter.setTransfers(transfersCheckBox.getValue());
        filter.setApprovedOnly(approvedOnlyCheckBox.getValue());
        filter.setDateFrom(dateFromBox.getValue());
        filter.setDateTo(dateToBox.getValue());
        for(Pair<AccountDto, AccountDto> accountsPair: accounts) {
            TransactionsFilterDto.AccountsInfo accountsInfo = new TransactionsFilterDto.AccountsInfo();
            if(accountsPair.getFirst() != null) {
                accountsInfo.setSrcAccountId(accountsPair.getFirst().getId());
            }
            if(accountsPair.getSecond() != null) {
                accountsInfo.setDstAccountId(accountsPair.getSecond().getId());
            }
            filter.addAccountInfo(accountsInfo);
        }
        for(CategoryDto.Type type: CategoryDto.Type.values()) {
            for(CategoryDto category: categories.get(type)) {
                filter.addCategoryId(type, category.getId());
            }
        }
        filter.addTags(tagsCloud.getValue());
        filter.setOrder(TransactionsFilterDto.Order.DESC);
        return filter;
    }

    private void fireChangeEvent() {
        getEventBus().fireEvent(new TransactionsFilterChangedEvent(TransactionsFilterPanel.this));
    }

    @Override
    public void onChange(TagsCloud control, Long[] values) {
        if(tagsCloud.equals(control)) {
            onFilterChange();
        }
    }
}
